package com.demo;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.SequenceFlow;
import org.flowable.engine.ProcessEngine;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.CollectionUtils;

import java.io.*;
import java.net.URL;
import java.util.*;

@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class FlowableOnline {

    @Autowired ProcessEngine processEngine;
    @Autowired RepositoryService repositoryService;
    @Autowired RuntimeService runtimeService;
    @Autowired TaskService taskService;

    // 《创建》页面，券商《broker1》，点《保存》按钮，启动工作流引擎
    @Test
    public void aStartFlow() {
        String processKey = "online_demo";

        String currentUser = "broker1";

        Map<String, Object> vars = new HashMap<>();
        vars.put("broker", currentUser);

        ProcessInstance instance = runtimeService.startProcessInstanceByKey(processKey, vars);
        log.info("instI d: {}, processDefName:{}, ", instance.getId(), instance.getProcessDefinitionName());
        saveCurrentProcessInstanceId(instance.getId());

        sendTodo();

        String prevActivityId = ((ExecutionEntityImpl) instance).getStartActivityId();
        String currActivityId = ((ExecutionEntityImpl) instance).getExecutions().get(0).getActivityId();
        SequenceFlow sequenceFlow = getSequenceFlow(instance.getProcessDefinitionId(), prevActivityId, currActivityId);
        log.info("sequenceFlow: {}", sequenceFlow);
    }

    //《待提交》页面，券商《broker1》点《提交》按钮
    @Test
    public void bWaitSubmit_submit() {
        String currentUser = "broker1";

        // 查询当前待办
        String procInstId = loadCurrentProcessInstanceId();
        TaskQuery query = taskService.createTaskQuery().processInstanceId(procInstId).taskAssignee(currentUser);
        Task task = query.singleResult();
        logCurrentTask(task);

        // 执行待办
        Map<String, Object> vars = new HashMap<>();
        vars.put("managers", Arrays.asList("manager1", "manager2"));

        String prevNodeCode = task.getTaskDefinitionKey();
        String prevNodeName = task.getName();

        taskService.complete(task.getId(), vars);

        // 执行完任务后，获取执行的线
        query = taskService.createTaskQuery().processInstanceId(procInstId);
        task = query.list().get(0);
        String currNodeCode = task.getTaskDefinitionKey();
        String currNodeName = task.getName();

        SequenceFlow sequenceFlow = getSequenceFlow(task.getProcessDefinitionId(), prevNodeCode, currNodeCode);
        log.info("sequenceFlow: {}", sequenceFlow);

        String lineCode = sequenceFlow.getId();
        String lineName = sequenceFlow.getName();

        // 执行后待办信息发送todo服务
        sendTodo();
    }

    // 《待答复》页面，咨询管理员《manager1》点《驳回》按钮
    @Test
    public void cWaitResponse_reject() {
        String currentUser = "manager1";

        // 查询当前待办
        String processInstanceId = loadCurrentProcessInstanceId();
        TaskQuery query = taskService.createTaskQuery().processInstanceId(processInstanceId).taskAssignee(currentUser);
        Task task = query.singleResult();
        logCurrentTask(task);

        String prevActivityId = task.getTaskDefinitionKey();

        // 执行待办
        Map<String, Object> vars = new HashMap<>();
        vars.put("manager", currentUser);
        vars.put("button", "reject");

        taskService.complete(task.getId(), vars);

        // 执行完任务后，获取执行的线
        query = taskService.createTaskQuery().processInstanceId(processInstanceId);
        task = query.list().get(0);
        String currActivityId = task.getTaskDefinitionKey();
        SequenceFlow sequenceFlow = getSequenceFlow(task.getProcessDefinitionId(), prevActivityId, currActivityId);
        log.info("sequenceFlow: {}", sequenceFlow);

        // 执行后待办信息发送todo服务
        sendTodo();
    }

    private SequenceFlow getSequenceFlow(String processDefinitionId, String prevActivityId, String currActivityId) {

        BpmnModel model = repositoryService.getBpmnModel(processDefinitionId);
        Collection<SequenceFlow> sequenceFlows = model.getMainProcess().findFlowElementsOfType(SequenceFlow.class);

        SequenceFlow sequenceFlow = null;
        for (SequenceFlow sequence : sequenceFlows) {
            if (((sequence.getSourceRef().equals(prevActivityId)) || sequence.getSourceRef().equals(prevActivityId + "Gateway"))
                    && sequence.getTargetRef().equals(currActivityId)) {
                sequenceFlow = sequence;
                break;
            }
        }

        return sequenceFlow;
    }

    // 《待补正》页面，券商《broker1》点《提交》按钮
    @Test
    public void dWaitAmend_submit() {
        String currentUser = "broker1";

        // 查询当前待办
        String processInstanceId = loadCurrentProcessInstanceId();
        TaskQuery query = taskService.createTaskQuery().processInstanceId(processInstanceId).taskAssignee(currentUser);
        Task task = query.singleResult();
        logCurrentTask(task);

        // 执行待办
        Map<String, Object> vars = new HashMap<>();
        vars.put("managers", Arrays.asList("manager1", "manager2"));

        taskService.complete(task.getId(), vars);

        // 执行后待办信息发送todo服务
        sendTodo();
    }

    // 《待答复》页面，咨询管理员《manager2》点《转发》按钮
    @Test
    public void eWaitResponse_forward() {
        String currentUser = "manager2";

        // 查询当前待办
        String processInstanceId = loadCurrentProcessInstanceId();
        TaskQuery query = taskService.createTaskQuery().processInstanceId(processInstanceId).taskAssignee(currentUser);
        Task task = query.singleResult();
        logCurrentTask(task);

        // 执行待办
        Map<String, Object> vars = new HashMap<>();
        vars.put("manager", currentUser);
        vars.put("button", "forward");
        vars.put("handlers", Arrays.asList("handler1", "handler2"));

        taskService.complete(task.getId(), vars);

        // 执行后待办信息发送todo服务
        sendTodo();
    }

    // 《待办理》页面，经办人《handler1》点《反馈》按钮
    @Test
    public void fWaitHandle_feedback() {
        String currentUser = "handler1";

        // 查询当前待办
        String processInstanceId = loadCurrentProcessInstanceId();
        TaskQuery query = taskService.createTaskQuery().processInstanceId(processInstanceId).taskAssignee(currentUser);
        Task task = query.singleResult();
        logCurrentTask(task);

        // 执行待办
        Map<String, Object> vars = new HashMap<>();
        vars.put("button", "feedback");

        taskService.complete(task.getId(), vars);

        // 执行后待办信息发送todo服务
        sendTodo();
    }

    // 《经办反馈》页面，咨询管理员《manager2》点《转发》按钮
    @Test
    public void gHandleFeedback_forward() {
        String currentUser = "manager2";

        // 查询当前待办
        String processInstanceId = loadCurrentProcessInstanceId();
        TaskQuery query = taskService.createTaskQuery().processInstanceId(processInstanceId).taskAssignee(currentUser);
        Task task = query.singleResult();
        logCurrentTask(task);

        // 执行待办
        Map<String, Object> vars = new HashMap<>();
        vars.put("button", "forward");
        vars.put("handlers", Arrays.asList("handler1", "handler2"));

        taskService.complete(task.getId(), vars);

        // 执行后待办信息发送todo服务
        sendTodo();
    }

    // 《待办理》页面，经办人《handler1》点《提交》按钮
    @Test
    public void hWaitHandle_submit() {
        String currentUser = "handler1";

        // 查询当前待办
        String processInstanceId = loadCurrentProcessInstanceId();
        TaskQuery query = taskService.createTaskQuery().processInstanceId(processInstanceId).taskAssignee(currentUser);
        Task task = query.singleResult();
        logCurrentTask(task);

        // 执行待办
        Map<String, Object> vars = new HashMap<>();
        vars.put("button", "submit");
        vars.put("leaders", Arrays.asList("leader1"));

        taskService.complete(task.getId(), vars);

        // 执行后待办信息发送todo服务
        sendTodo();
    }

    // 《待审批》页面，领导《leader1》点《驳回》按钮
    @Test
    public void iWaitApprove_reject() {
        String currentUser = "leader1";

        // 查询当前待办
        String processInstanceId = loadCurrentProcessInstanceId();
        TaskQuery query = taskService.createTaskQuery().processInstanceId(processInstanceId).taskAssignee(currentUser);
        Task task = query.singleResult();
        logCurrentTask(task);

        // 执行待办
        Map<String, Object> vars = new HashMap<>();
        vars.put("button", "reject");
        vars.put("handlers", Arrays.asList("handler1", "handler2"));

        taskService.complete(task.getId(), vars);

        // 执行后待办信息发送todo服务
        sendTodo();
    }

    // 《待办理》页面，经办人《handler1》点《提交》按钮
    @Test
    public void jWaitHandle_submit2() {
        String currentUser = "handler1";

        // 查询当前待办
        String processInstanceId = loadCurrentProcessInstanceId();
        TaskQuery query = taskService.createTaskQuery().processInstanceId(processInstanceId).taskAssignee(currentUser);
        Task task = query.singleResult();
        logCurrentTask(task);

        // 执行待办
        Map<String, Object> vars = new HashMap<>();
        vars.put("button", "submit");
        vars.put("leaders", Arrays.asList("leader1"));

        taskService.complete(task.getId(), vars);

        // 执行后待办信息发送todo服务
        sendTodo();
    }

    // 《待审批》页面，领导《leader1》点《提交》按钮
    @Test
    public void kWaitApprove_submit() {
        String currentUser = "leader1";

        // 查询当前待办
        String processInstanceId = loadCurrentProcessInstanceId();
        TaskQuery query = taskService.createTaskQuery().processInstanceId(processInstanceId).taskAssignee(currentUser);
        Task task = query.singleResult();
        logCurrentTask(task);

        // 执行待办
        Map<String, Object> vars = new HashMap<>();
        vars.put("button", "submit");

        taskService.complete(task.getId());

        // 执行后待办信息发送todo服务
        sendTodo();
    }

    // 《待确认》页面，咨询管理员《manager2》点《提交》按钮
    @Test
    public void lWaitConfirm_submit() {
        String currentUser = "manager2";

        // 查询当前待办
        String processInstanceId = loadCurrentProcessInstanceId();
        TaskQuery query = taskService.createTaskQuery().processInstanceId(processInstanceId).taskAssignee(currentUser);
        Task task = query.singleResult();
        logCurrentTask(task);

        // 执行待办
        taskService.complete(task.getId());

        // 执行后待办信息发送todo服务
        sendTodo();
    }

    /* 打开此注释，将流程办结
    // 《待查看》页面，券商《broker1》点《提交》按钮
    @Test
    public void mWaitCheck_submit() {
        String currentUser = "broker1";

        // 查询当前待办
        String processInstanceId = loadCurrentProcessInstanceId();
        TaskQuery query = taskService.createTaskQuery().processInstanceId(processInstanceId).taskAssignee(currentUser);
        Task task = query.singleResult();
        logCurrentTask(task);

        // 执行待办
        taskService.complete(task.getId());

        // 执行后待办信息发送todo服务
        sendTodo();
    }*/

    private void sendTodo() {
        // 查询所有待办信息,发送todo生成待办
        String processInstanceId = loadCurrentProcessInstanceId();
        TaskQuery query = taskService.createTaskQuery().processInstanceId(processInstanceId);
        List<Task> tasks = query.list();
        for (Task task : tasks) {
            log.info("****** next node task info, procDefId: {}, nodeKey: {}", task.getProcessDefinitionId(), task.getTaskDefinitionKey());
            log.info("       nodeName: {}, assignee: {}, taskId: {}", task.getName(), task.getAssignee(), task.getId());
        }
        // 将所有的待办任务发送todo服务,由todo生成待办信息,如果查询不到待办,则向todo发送办结信息
        List<TodoTask> todoTasks = convertTodoTask(tasks);
        for (TodoTask todoTask : todoTasks) {
            log.info("准备发送的待办信息:{}", JSON.toJSONString(todoTask));
        }
    }

    private List<TodoTask> convertTodoTask(List<Task> tasks) {
        List<TodoTask> todoTasks = new ArrayList<>();
        if (CollectionUtils.isEmpty(tasks)) {
            return Collections.emptyList();
        }

        String processDefinitionId = tasks.get(0).getProcessDefinitionId();
        String[] items = processDefinitionId.split(":");
        if (items.length != 3) {
            throw new RuntimeException("流程定义ID格式错误");
        }

        String[] names = items[0].split("_");
        if (names.length != 2) {
            throw new RuntimeException("流程定义ID格式错误");
        }

        String sysCode = names[0];
        String flowCode = names[1];

        String procDefName = repositoryService.createProcessDefinitionQuery()
                .processDefinitionId(processDefinitionId).singleResult().getName();

        items = procDefName.split("_");
        if (items.length != 2) {
            throw new RuntimeException("流程定义名称格式错误");
        }
        String sysName = items[0];
        String flowName = items[1];

        for (Task task : tasks) {
            TodoTask todoTask = new TodoTask();
            todoTask.setLoginName(task.getAssignee());
            todoTask.setBusiId(""); // TODO
            todoTask.setSysCode(sysCode);
            todoTask.setSysName(sysName);
            todoTask.setFlowCode(flowCode);
            todoTask.setFlowName(flowName);
            todoTask.setNodeCode(task.getTaskDefinitionKey());
            todoTask.setNodeName(task.getName());
            todoTasks.add(todoTask);
        }

        return todoTasks;
    }

    private String loadCurrentProcessInstanceId() {
        String filePath = getProcessInstanceIdFilePath();

        BufferedReader br = null;
        String processId = null;
        try {
            br = new BufferedReader(new InputStreamReader(new FileInputStream(filePath), "UTF-8"));
            processId = br.readLine();
        } catch (Exception ingored) {
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException ingored) {
                }
            }
        }

        return processId;
    }

    private void saveCurrentProcessInstanceId(String id) {
        String filePath = getProcessInstanceIdFilePath();
        BufferedWriter bw = null;
        try {
            bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filePath), "UTF-8"));
            bw.write(id);
        } catch (Exception ingored) {
        } finally {
            if (bw != null) {
                try {
                    bw.close();
                } catch (IOException ingored) {
                }
            }
        }
    }

    private String getProcessInstanceIdFilePath() {
        ClassLoader classLoader = FlowableOnline.class.getClassLoader();
        URL resource = classLoader.getResource("application.yml");
        String fullName = resource.getPath();
        String path = new File(fullName).getParent();
        return path + File.separator + "processId.txt";
    }

    private void logCurrentTask(Task task) {
        log.info("****** current node task info, procDefId: {}, nodeKey: {}", task.getProcessDefinitionId(), task.getTaskDefinitionKey());
        log.info("       nodeName: {}, assignee: {}, taskId: {}", task.getName(), task.getAssignee(), task.getId());
    }
}
